package com.idanchuang.support.etcache;

import com.idanchuang.support.etcache.local.LocalCache;
import com.idanchuang.support.etcache.local.LocalCacheConfig;
import com.idanchuang.support.etcache.local.LocalCacheFactory;
import com.idanchuang.support.etcache.pressure.PressureChecker;
import com.idanchuang.support.etcache.pubsub.*;
import com.idanchuang.support.etcache.remote.RemoteCache;
import com.idanchuang.support.etcache.remote.RemoteCacheConfig;
import com.idanchuang.support.etcache.remote.RemoteCacheFactory;
import com.idanchuang.support.etcache.remote.codec.RemoteKeyCodec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 缓存对象管理
 *
 * @author yjy
 * Created at 2021/12/21 9:51 上午
 */
public class EtCacheManager implements DisposableBean {

    private static final Logger logger = LoggerFactory.getLogger(EtCacheManager.class);
    private final RemoteKeyCodec remoteKeyCodec;
    private final RemoteEventPublisher publisher;
    private final RemoteEventSubscriber subscriber;
    private final LocalCacheFactory localCacheFactory;
    private final RemoteCacheFactory remoteCacheFactory;
    private final PressureChecker pressureChecker;
    private final SubEventQueue subEventQueue;

    private final EtCacheProperties properties;

    public EtCacheManager(RemoteKeyCodec remoteKeyCodec,
                          RemoteEventSubscriber remoteEventSubscriber,
                          RemoteEventPublisher remoteEventPublisher,
                          LocalCacheFactory localCacheFactory, RemoteCacheFactory remoteCacheFactory,
                          PressureChecker pressureChecker, SubEventQueue subEventQueue,
                          EtCacheProperties properties) {
        this.remoteKeyCodec = remoteKeyCodec;
        this.publisher = remoteEventPublisher;
        this.subscriber = remoteEventSubscriber;
        this.localCacheFactory = localCacheFactory;
        this.remoteCacheFactory = remoteCacheFactory;
        this.pressureChecker = pressureChecker;
        this.subEventQueue = subEventQueue;
        this.properties = properties;
    }

    /**
     * 缓存对象集合
     */
    private final Map<String, Cache<?, ?>> etCaches = new ConcurrentHashMap<>();

    public <K, V> Cache<K, V> getCache(String cacheName) {
        return getCache(cacheName, null, null, null, true);
    }

    public <K, V> Cache<K, V> getCache(String cacheName, boolean createIfNotExist) {
        return getCache(cacheName, null, null, null, createIfNotExist);
    }

    public <K, V> Cache<K, V> getCache(String cacheName, CacheLoader<K, V> cacheLoader) {
        return getCache(cacheName, null, null, cacheLoader, true);
    }

    public <K, V> Cache<K, V> getCache(String cacheName,
                                       LocalCacheConfig localCacheConfig) {
        return getCache(cacheName, localCacheConfig, null, null, true);
    }

    public <K, V> Cache<K, V> getCache(String cacheName,
                                       RemoteCacheConfig remoteCacheConfig) {
        // et cache
        return getCache(cacheName, null, remoteCacheConfig, null, true);
    }

    public <K, V> Cache<K, V> getCache(String cacheName,
                                       LocalCacheConfig localCacheConfig,
                                       RemoteCacheConfig remoteCacheConfig) {
        // et cache
        return getCache(cacheName, localCacheConfig, remoteCacheConfig, null, true);
    }

    /**
     * 获取一个缓存对象, 如果不存在则新建, 如果已存在则返回
     * @param cacheName 缓存名称
     * @param localCacheConfig 本地缓存配置
     * @param remoteCacheConfig 远程缓存配置
     * @param cacheLoader 缓存加载器
     * @param createIfNotExist 不存在时是否自动创建
     * @param <K> key类型
     * @param <V> value类型
     * @return 缓存对象
     */
    public <K, V> Cache<K, V> getCache(final String cacheName,
                                       final LocalCacheConfig localCacheConfig,
                                       final RemoteCacheConfig remoteCacheConfig,
                                       final CacheLoader<K, V> cacheLoader,
                                       final boolean createIfNotExist) {
        if (createIfNotExist) {
            return (Cache<K, V>) etCaches.computeIfAbsent(cacheName, (cn) -> this.newCache(cn, localCacheConfig, remoteCacheConfig, cacheLoader));
        } else {
            return (Cache<K, V>) etCaches.get(cacheName);
        }
    }

    /**
     * 销毁一个缓存对象
     * @param cacheName 缓存名称
     */
    public void destroyCache(final String cacheName) {
        Cache cache = etCaches.remove(cacheName);
        logger.info("destroy cache by name: {}, cache is exist: {}", cacheName, cache != null);
        if (cache != null) {
            cache.destroy();
        }
    }

    /**
     * 新建一个缓存对象
     * @param cacheName 缓存名称
     * @param localCacheConfig 本地缓存配置
     * @param remoteCacheConfig 远程缓存配置
     * @param cacheLoader 缓存加载器
     * @param <K> key类型
     * @param <V> value类型
     * @return 缓存对象
     */
    private  <K, V> Cache<K, V> newCache(String cacheName,
                                       LocalCacheConfig localCacheConfig,
                                       RemoteCacheConfig remoteCacheConfig,
                                       CacheLoader<K, V> cacheLoader) {
       try {
           logger.info("new cache with name: {}", cacheName);
           // local cache
           if (localCacheConfig == null) {
               localCacheConfig = new LocalCacheConfig(cacheName, pressureChecker);
           }
           LocalCache<K, V> localCache = localCacheFactory.getBean(localCacheConfig);
           // remote cache
           if (remoteCacheConfig == null) {
               remoteCacheConfig = new RemoteCacheConfig(cacheName,
                       remoteKeyCodec, publisher, subscriber, pressureChecker, properties);
           }
           RemoteCache<K, V> remoteCache = remoteCacheFactory.getBean(remoteCacheConfig);
           // et cache
           return EtCacheBuilder.<K, V>newBuilder(cacheName, this)
                   .setLocalCache(localCache).setRemoteCache(remoteCache)
                   .build(cacheLoader);
       } catch (Exception e) {
           throw new RuntimeException(e);
       }
    }

    @Override
    public void destroy() throws Exception {
        logger.info("destroy all et caches...cache count: {}", etCaches.size());
        etCaches.forEach((k, v) -> v.destroy());
    }

    private static class EtCacheBuilder<K, V> {

        private final String cacheName;
        private final EtCacheManager manager;
        private LocalCache<K, V> localCache;
        private RemoteCache<K, V> remoteCache;

        public static <K, V> EtCacheBuilder<K, V> newBuilder(String cacheName, EtCacheManager manager) {
            return new EtCacheBuilder<>(cacheName, manager);
        }

        public EtCacheBuilder(String cacheName, EtCacheManager manager) {
            this.cacheName = cacheName;
            this.manager = manager;
        }

        public EtCacheBuilder<K, V> setLocalCache(LocalCache<K, V> localCache) {
            this.localCache = localCache;
            return this;
        }

        public EtCacheBuilder<K, V> setRemoteCache(RemoteCache<K, V> remoteCache) {
            this.remoteCache = remoteCache;
            return this;
        }

        public EtCache<K, V> build() {
            return build(null);
        }

        public EtCache<K, V> build(CacheLoader<K, V> cacheLoader) {
            return new EtCache<K, V>(manager, cacheName, localCache, remoteCache, cacheLoader);
        }

    }

    public RemoteKeyCodec getRemoteKeyCodec() {
        return remoteKeyCodec;
    }

    public RemoteCacheFactory getRemoteCacheFactory() {
        return remoteCacheFactory;
    }

    public LocalCacheFactory getLocalCacheFactory() {
        return localCacheFactory;
    }

    public PressureChecker getPressureChecker() {
        return pressureChecker;
    }

    public SubEventQueue getSubEventQueue() {
        return subEventQueue;
    }

    public Map<String, Cache<?, ?>> getEtCaches() {
        return etCaches;
    }

    public RemoteEventPublisher getPublisher() {
        return publisher;
    }

    public RemoteEventSubscriber getSubscriber() {
        return subscriber;
    }

    public EtCacheProperties getProperties() {
        return properties;
    }
}
